#include "common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"
#include "semphr.h"
#include "em_usart.h"
#include "em_cmu.h"
#include "em_ldma.h"
#include "lwrb.h"

#include "bsp_usart.h"
TaskHandle_t console_task_handle;
#define StreamBufferTotalBytes              128
#define StreamBufferTriggerLevelBytes       5
static SemaphoreHandle_t console_mutex;

static struct dma_recv_instance console_recv;
static struct dma_send_instance console_send;

/* Ping-Pong buffer size and constant for Ping-Pong transfer */
#define PP_BUFFER_SIZE      64

/* Descriptor linked list for LDMA transfer */
static LDMA_Descriptor_t descLink[2];

/* Buffer for Ping-Pong transfer */

static struct ldmaBuffer_t
{
    uint8_t pingBuffer[PP_BUFFER_SIZE];
    uint8_t pongBuffer[PP_BUFFER_SIZE];
}ldmaBuffer;

static uint8_t console_buf[PP_BUFFER_SIZE * 2];

/**************************************************************************
 * @brief Initialize USART0
 * USART0 USART0 中断发送
 **************************************************************************/
static void bsp_USART0Init (void)
{
     /* Configure GPIO pins */
    CMU_ClockEnable(cmuClock_GPIO, true);
    CMU_ClockEnable(cmuClock_HFPER, true);
    CMU_ClockEnable(cmuClock_USART0, true);
    /* To avoid false start, configure output as high */
    GPIO_PinModeSet(USART0_TX_PORT, USART0_TX_PIN, gpioModePushPull, 1);
    GPIO_PinModeSet(USART0_RX_PORT, USART0_RX_PIN, gpioModeInputPull, 1);
//    GPIO_ExtIntConfig(USART0_RX_PORT, USART0_RX_PIN, USART0_RX_PIN, 1, 1, true);
    USART_InitAsync_TypeDef config = USART_INITASYNC_DEFAULT; //异步模式
    // Initialize USART asynchronous mode and route pins
    config.baudrate = USART0_BAUDRATE;                                // 1Mbps
    USART_InitAsync(USART0, &config);
    USART0->ROUTELOC0 = USART0_ROUTELOC;
    USART0->ROUTEPEN = USART0_ROUTEPEN;
#if USART0_RX_IRQ
    NVIC_EnableIRQ(USART0_RX_IRQn);
    //USART_IntDisable(USART0, USART_IEN_RXDATAV);
    USART_IntEnable(USART0, USART_IEN_RXDATAV);
#endif
#if USART0_TX_IRQ
    USART_IntEnable(USART0, USART_IEN_TXC);
    NVIC_EnableIRQ(USART0_TX_IRQn);
#endif
    USART_IntClear(USART0, USART_IntGet(USART0));
#if 0 
    /* Enable ODD interrupt*/
    NVIC_ClearPendingIRQ(GPIO_ODD_IRQn);
    NVIC_EnableIRQ(GPIO_ODD_IRQn);
#endif
}


/**************************************************************************//**
 * @brief USART0 TX IRQ Handler
 *****************************************************************************/
void USART0_TX_IRQHandler(void)
{
    uint32_t flags;
    flags = USART_IntGet(USART0);
    USART_IntClear(USART0, flags);
    if(flags & USART_IF_TXC)
    {
        console_send.transmit_status = IDLE;
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        vTaskNotifyGiveFromISR(console_task_handle, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
}

void USART0_RX_IRQHandler(void)
{
    uint32_t flags;
    flags = USART_IntGet(USART0);
    USART_IntClear(USART0, flags);
    if(flags & USART_IF_RXDATAV)
    {
    }
}

/***************************************************************************//**
 * @brief
 *   Descriptor linked list example.
 ******************************************************************************/
static void initLdma(void)
{
    CMU_ClockEnable(cmuClock_LDMA, true);
    LDMA_Init_t init = LDMA_INIT_DEFAULT;
    LDMA_Init( &init );
}

/* Use peripheral transfer configuration macro */
LDMA_TransferCfg_t periTransferRx = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART0_RXDATAV);


static void initRecipientLdma(void)
{
    /* LINK descriptor macros for Ping-Pong transfer */
    descLink[0] = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(&(USART0->RXDATA), &ldmaBuffer.pingBuffer, PP_BUFFER_SIZE, 1);
    descLink[1] = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(&(USART0->RXDATA), &ldmaBuffer.pongBuffer, PP_BUFFER_SIZE, -1);

    /* Enable interrupts */
    descLink[0].xfer.doneIfs = false;
    descLink[1].xfer.doneIfs = false;

    /* Disable automatic transfers */
    descLink[0].xfer.structReq = false;
    descLink[1].xfer.structReq = false;

    LDMA_StartTransfer(LDMA_USART0_RX_CHANNEL, (void*)&periTransferRx, (void*)&descLink);

    /* Software request to start transfer */
    LDMA->SWREQ |= LDMA_USART0_RX_CH_MASK;
}

/* Use peripheral transfer configuration macro */
static LDMA_TransferCfg_t periTransferTx = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART0_TXBL);
static LDMA_Descriptor_t descSenderLink;

static void initSenderLdma(void)
{
    descSenderLink = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_SINGLE_M2P_BYTE(NULL, &(USART0->TXDATA), 1);
    descSenderLink.xfer.doneIfs = false;
    /* Disable automatic transfers */
    descSenderLink.xfer.structReq = false;
}

static void SenderLdma(uint8_t *src, uint16_t lens)
{
    descSenderLink.xfer.srcAddr = (uint32_t) src;
    descSenderLink.xfer.xferCnt = lens - 1;
    /* Software request to start transfer */
    LDMA_StartTransfer(LDMA_USART0_TX_CHANNEL, (void*)&periTransferTx, (void*)&descSenderLink);
    // LDMA->SWREQ |= LDMA_USART0_TX_CH_MASK;
}



static void console_init(void)
{
    bsp_USART0Init();
    initLdma();
    dma_usart_recv_instance_init(&console_recv, 
                                                        console_buf, 
                                                        PP_BUFFER_SIZE * 2, 
                                                        ldmaBuffer.pingBuffer, 
                                                        LDMA_USART0_RX_CHANNEL);
    dma_usart_send_instance_init(&console_send,
                                                StreamBufferTotalBytes, 
                                                StreamBufferTriggerLevelBytes, 
                                                LDMA_USART0_TX_CHANNEL);
    initRecipientLdma();
    initSenderLdma();

}

static int console_write(elab_device_t *me, const char * buff, size_t lens)
{
  (void) me;
    xSemaphoreTake(console_mutex, portMAX_DELAY);
    int ret, j;
    for (j = 0; j < lens ; j ++ )
    {
        USART_Tx(USART0, buff[j]);
    }
    ret = lens;
    xSemaphoreGive(console_mutex);
    return ret;
}

static int w[2] = {0};
static int console_write_noblock(elab_device_t *me, const char * buff, size_t lens)
{
  (void) me;
    w[0] += lens;
    xSemaphoreTake(console_mutex, portMAX_DELAY);

    int ret = 0;
    int total = 0;
    int try = 10;
    do
    {
        ret = xStreamBufferSend(console_send.stream, buff + total, lens, 0);
        if(!ret)
        {
            int left = xStreamBufferSpacesAvailable(console_send.stream);
            if(left > 0)
            {
                ret = xStreamBufferSend(console_send.stream, 
                                                                buff + total, 
                                                                lens, 
                                                                0);
            }
        }
        lens -= ret;
        total += ret;
        if(lens <= 0)
        {
          break;
        }
        vTaskDelay(10);
    }
    while(--try);
    if(lens > 0)
      {
        lens = lens;
        try = 1;
      }
    xSemaphoreGive(console_mutex);
    return 0;
}


static int console_read(elab_device_t *me, char * buff, size_t lens)
{
  (void) me;
    int ret = lwrb_read(&console_recv.rb, buff, lens);
    return ret;
}

static int console_select(elab_device_t *me, uint32_t attr)
{
  (void) me;
  (void) attr;
    int ret = dmaMsgStore(&console_recv);
    return ret;
}

elab_device_t console = {
    .attr = {.name = "console"},
    .user_data = NULL,
};

static const struct file_operations ops = {
    .write = console_write_noblock,
    .read = console_read,
    .open = NULL,
    .release = NULL,
    .select = console_select,
};


static uint8_t console_tx[64];
static uint8_t console_tx_cache[64];
static void console_tx_task(void *param)
{
    (void) param;
    size_t xReceivedBytes;
    while(1)
    {
        xReceivedBytes =  xStreamBufferReceive( console_send.stream, console_tx, sizeof(console_tx), 100);
        
        if(xReceivedBytes > 0)
        {
            if(console_send.transmit_status == BUSY)
            {
                ulTaskNotifyTake( pdTRUE, /* Clear the notification value on exit. */
                                      20 );/* Block indefinitely. */
            }
            if(console_send.transmit_status == IDLE)
            {
                ulTaskNotifyTake( pdTRUE, /* Clear the notification value on exit. */
                                        0);/* Block indefinitely. */
                memcpy(console_tx_cache, console_tx, xReceivedBytes);
                w[1] += xReceivedBytes;
                console_send.transmit_status = BUSY;
                SenderLdma(console_tx_cache, xReceivedBytes);
            }
        }
    }
}

static void console_register(void)
{
    console_init();
    console_mutex = xSemaphoreCreateMutex();
    if (xTaskCreate(console_tx_task, "console_tx", 128, NULL, 2, &console_task_handle) != pdPASS)
    {
        elog_error("shell task creat failed");
    }
    console.ops = &ops;

    device_register(&console);
}

INIT_EXPORT(console_register, EXPORT_DRVIVER);

